package com.liusu.file.utils;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.liusu.common.constant.HttpStatus;
import com.liusu.common.exception.ServiceException;
import com.liusu.common.utils.file.FileUploadUtils;
import com.liusu.file.config.MinioConfig;
import com.liusu.file.dto.*;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import io.minio.messages.Part;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

@Component
@Slf4j
public class MinioUtil {
    @Autowired
    private MinioConfig prop;

    @Resource
    private MinioClient minioClient;
    @Resource
    private CustomMinioClient customMinioClient;

    /**
     * 查看存储bucket是否存在
     *
     * @return boolean
     */
    public Boolean bucketExists(String bucketName) {
        Boolean found;
        try {
            found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return found;
    }

    /**
     * 创建存储bucket
     *
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 删除存储bucket
     *
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 获取全部bucket
     */
    public List<Bucket> getAllBuckets() {
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            return buckets;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 文件上传
     *
     * @param file 文件
     * @return Boolean
     */
    public String upload(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename)) {
            throw new RuntimeException();
        }
        String objectName = FileUploadUtils.extractFilename(file);
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
            //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectName;
    }

    /**
     * 获取文件地址
     *
     * @param fileName
     * @return
     */
    public String getObjectUrl(String fileName) {
        // 查看文件地址
        GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs
                .builder()
                .bucket(prop.getBucketName())
                .object(fileName)
                .method(Method.GET)
                .build();
        try {
            return minioClient.getPresignedObjectUrl(build);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 文件下载
     *
     * @param fileName 文件名称
     * @param res      response
     * @return Boolean
     */
    public void download(String fileName, HttpServletResponse res) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(prop.getBucketName())
                .object(fileName).build();
        try (GetObjectResponse response = minioClient.getObject(objectArgs)) {
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
                while ((len = response.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                res.setCharacterEncoding("utf-8");
                // 设置强制下载不打开
                // res.setContentType("application/force-download");
                res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
                try (ServletOutputStream stream = res.getOutputStream()) {
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 查看文件对象
     *
     * @return 存储bucket内文件对象信息
     */
    public List<Item> listObjects() {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(prop.getBucketName()).build());
        List<Item> items = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                items.add(result.get());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return items;
    }

    /**
     * 删除
     *
     * @param fileName
     * @return
     * @throws Exception
     */
    public boolean remove(String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(prop.getBucketName()).object(fileName).build());
        } catch (Exception e) {
            return false;
        }
        return true;
    }


    public MultipartUploadCreateResponse createMultipartUpload(MultipartUploadCreateRequest request) {

        if (StringUtils.isBlank(request.getFileName())) {
            throw new RuntimeException();
        }
        String objectName = FileUploadUtils.extractFilename(request.getFileName());
        Multimap<String, String> headers = HashMultimap.create();
        headers.put("Content-Type", request.getContentType());
        MultipartUploadCreate uploadCreate = MultipartUploadCreate.builder()
                .bucketName(prop.getBucketName())
                .objectName(objectName)
                .headers(headers)
                .build();
        String uploadId = uploadId(uploadCreate).result().uploadId();
        log.info("文件上传id：{}，文件名：{}", uploadId, objectName);
        return MultipartUploadCreateResponse.builder()
                .uploadId(uploadId)
                .fileName(objectName)
                .chunks(request.getChunks())
                .build();
    }

    /**
     * 跟据文件信息返回唯一的上传ID
     *
     * @param uploadCreate 文件上传的信息
     * @return 包含上传ID的响应
     * @throws ServiceException 如果在上传过程中发生错误
     */
    public CreateMultipartUploadResponse uploadId(MultipartUploadCreate uploadCreate) {
        try {
            return customMinioClient.createMultipartUploadAsync(
                    uploadCreate.getBucketName(),
                    uploadCreate.getRegion(),
                    uploadCreate.getObjectName(),
                    uploadCreate.getHeaders(),
                    uploadCreate.getExtraQueryParams()
            ).get();
        } catch (Exception e) {
            log.error("获取文件上传编号失败", e);
            throw new ServiceException("获取文件上传编号失败", HttpStatus.ERROR);
        }
    }


    public UploadPartResponse uploadPart(UploadPartRequest request) {
        try (InputStream in = request.getFile().getInputStream()) {

            return customMinioClient.uploadPartAsync(
                    prop.getBucketName(),
                    null,
                    request.getFileName(),
                    in,
                    request.getFile().getSize(),
                    request.getUploadId(),
                    request.getPartNumber(),
                    null,
                    null
            ).get();

        } catch (Exception e) {
            log.error("分片上传文件流处理失败", e);
            throw new ServiceException("上传分片失败", HttpStatus.ERROR);
        }
    }

    /**
     * 上传文件分片
     *
     * @param uploadRequest 上传分片的信息
     * @return 上传结果
     */
    public String completeMultipartUpload(CompleteMultipartUploadRequest uploadRequest) {
        try {
            ListPartsResponse listMultipart = listMultipartUploads(MultipartUploadCreate.builder()
                    .bucketName(prop.getBucketName())
                    .objectName(uploadRequest.getFileName())
                    .maxParts(uploadRequest.getChunks() + 10)
                    .uploadId(uploadRequest.getUploadId())
                    .partNumberMarker(0)
                    .build());
            Part[] parts = listMultipart.result().partList().toArray(new Part[]{});
            customMinioClient.completeMultipartUploadAsync(
                    prop.getBucketName(),
                    null,
                    uploadRequest.getFileName(),
                    uploadRequest.getUploadId(),
                    parts,
                    null,
                    null
            ).get();

            return uploadRequest.getFileName();
        } catch (Exception e) {
            log.error("合并分片失败", e);
            throw new ServiceException("合并分片失败", HttpStatus.ERROR);
        }
    }

    /**
     * 获取已上传的文件分片信息
     *
     * @param uploadCreate 上传文件的信息
     * @return 已上传的分片
     */
    public ListPartsResponse listMultipartUploads(MultipartUploadCreate uploadCreate) {
        try {
            return customMinioClient.listPartsAsync(
                    uploadCreate.getBucketName(),
                    uploadCreate.getRegion(),
                    uploadCreate.getObjectName(),
                    uploadCreate.getMaxParts(),
                    uploadCreate.getPartNumberMarker(),
                    uploadCreate.getUploadId(),
                    uploadCreate.getHeaders(),
                    uploadCreate.getExtraQueryParams()
            ).get();
        } catch (Exception e) {
            log.error("获取分片列表失败", e);
            throw new ServiceException("获取分片列表失败", HttpStatus.ERROR);
        }
    }
}
